Paint.Net笔记(X4) Effect

上一篇讲菜单项中的Action 与 HistoryFunction. 两者之间的区别, 暂时还不甚了然. 先放一放, 这一篇整理一下Paint.Net中的Effect

Effect是以Plugin的方式提供的, 可以使用单独的dll, 放在特定的目录下. 程序启动时, 会自动加载.但Paint.Net并不是插件结构. 和SharpDevelop不同, SharpDevelop中界面的UI也是以Plugin的方式提供的.

Effect滤镜的主要逻辑在EffectMenuBase.cs中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void EffectMenuItem_Click(object sender, EventArgs e)
{
if (AppWorkspace.ActiveDocumentWorkspace == null)
{
return;
}

PdnMenuItem pmi = (PdnMenuItem)sender;
Type effectType = (Type)pmi.Tag;

RunEffect(effectType);
}

public void RunEffect(Type effectType)
{
bool oldDirtyValue = AppWorkspace.ActiveDocumentWorkspace.Document.Dirty;
bool resetDirtyValue = false;

AppWorkspace.Update(); // make sure the window is done 'closing'
AppWorkspace.Widgets.StatusBarProgress.ResetProgressStatusBar();
DocumentWorkspace activeDW = AppWorkspace.ActiveDocumentWorkspace;

PdnRegion selectedRegion; //选择区域,滤镜是应用于整幅图像,还是一个选择区域。下图就是只应用于一个选择区域

if (activeDW.Selection.IsEmpty)
{
selectedRegion = new PdnRegion(activeDW.Document.Bounds);
}
else
{
selectedRegion = activeDW.Selection.CreateRegion();
}

这里Effect通过EffectFlags.Configurable分为2类(有没有参数配置,可以调整参数)

第一类(没有参数配置):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
if (!(effect.CheckForEffectFlags(EffectFlags.Configurable)))
{
Surface copy = activeDW.BorrowScratchSurface(this.GetType() + ".RunEffect() using scratch surface for non-configurable rendering");

try
{
using (new WaitCursorChanger(AppWorkspace))
{
copy.CopySurface(layer.Surface);
}

EffectEnvironmentParameters eep = new EffectEnvironmentParameters(
AppWorkspace.AppEnvironment.PrimaryColor,
AppWorkspace.AppEnvironment.SecondaryColor,
AppWorkspace.AppEnvironment.PenInfo.Width,
selectedRegion,
copy);

effect.EnvironmentParameters = eep;

DoEffect(effect, null, selectedRegion, selectedRegion, copy, out exception);
}

finally
{
activeDW.ReturnScratchSurface(copy);
}
}

第二类(有参数配置):有参数配置,就需要一个调整参数的对话框,例如上图中的高斯模糊. 先是创建一个EffectConfigDialog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

using (EffectConfigDialog configDialog = effect.CreateConfigDialog())
{
configDialog.Opacity = 0.9;
configDialog.Effect = effect;
configDialog.EffectSourceSurface = originalSurface;
configDialog.Selection = selectedRegion;

BackgroundEffectRenderer ber = null;

EventHandler eh =
delegate(object sender, EventArgs e)
{
EffectConfigDialog ecf = (EffectConfigDialog)sender;

if (ber != null)
{
AppWorkspace.Widgets.StatusBarProgress.ResetProgressStatusBarAsync();

try
{
ber.Start();
}

catch (Exception ex)
{
exception = ex;
ecf.Close();
}
}
};

configDialog.EffectTokenChanged += eh;
}

如何创建EffectConfigDialog应该是Paint.Net中最有技巧性的东西了,真是大开眼界

翻看了不少代码,大致明白了是怎么回事。因为Effect在Paint.Net中是以plugin的形式存在,假如Effect有参数可以调整,那么在什么地方去调整参数呢?想像一下,我们需要有一个参数的Dialog。在没有源码的情况下,如何创建这个Dialog。以高斯模糊为例,只要该Effect提供参数,由Paint.Net帮你创建EffectConfigDialog

Paint.Net在框架中提供了一种参数与Control的一一对应,然后根据参数列表,创建一个一个的Control,然后用一个Panel将这些Control添加进来。再提供一个参数变更引发的事件。
这些参数用一个叫做EffectConfigToken的类封装起来(具体是PropertyCollection)

以高斯模糊为例

1
2
3
4
5
6
7
8
9
10
11
public sealed class GaussianBlurEffect
: InternalPropertyBasedEffect

public abstract class InternalPropertyBasedEffect
: PropertyBasedEffect

public abstract class PropertyBasedEffect
: Effect<PropertyBasedEffectConfigToken>

public sealed class PropertyBasedEffectConfigToken
: EffectConfigToken

其中

1
2
internal sealed class PropertyBasedEffectConfigDialog
: EffectConfigDialog<PropertyBasedEffect, PropertyBasedEffectConfigToken>

AmountEffectConfigDialog.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void amountUpDown_ValueChanged(object sender, System.EventArgs e)
{
if (amountTrackBar.Value != (int)amountUpDown.Value)
{
amountTrackBar.Value = (int)amountUpDown.Value;
FinishTokenUpdate();
}
}

public void FinishTokenUpdate()
{
InitTokenFromDialog();
OnEffectTokenChanged();
}

protected virtual void OnEffectTokenChanged()
{
if (EffectTokenChanged != null)
{
EffectTokenChanged(this, EventArgs.Empty);
}
}

扫描有多少个Effect,这部分是以插件的形式出现的,加载后出现在菜单项中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
private static EffectsCollection GatherEffects()
{
List<Assembly> assemblies = new List<Assembly>();

// PaintDotNet.Effects.dll
assemblies.Add(Assembly.GetAssembly(typeof(Effect)));

// TARGETDIR\Effects\*.dll
string homeDir = PdnInfo.GetApplicationDir();
string effectsDir = Path.Combine(homeDir, InvariantStrings.EffectsSubDir);
bool dirExists;

try
{
dirExists = Directory.Exists(effectsDir);
}

catch
{
dirExists = false;
}

if (dirExists)
{
string fileSpec = "*" + InvariantStrings.DllExtension;
string[] filePaths = Directory.GetFiles(effectsDir, fileSpec);

foreach (string filePath in filePaths)
{
Assembly pluginAssembly = null;

try
{
pluginAssembly = Assembly.LoadFrom(filePath);
assemblies.Add(pluginAssembly);
}

catch (Exception ex)
{
Tracing.Ping("Exception while loading " + filePath + ": " + ex.ToString());
}
}
}

EffectsCollection ec = new EffectsCollection(assemblies);
return ec;
}